package com.buli.blmall.admin.config;

import com.buli.blmall.admin.common.response.Result;
import com.buli.blmall.admin.consts.Constants;
import com.buli.blmall.admin.consts.PathConstst;
import com.buli.blmall.admin.core.filter.AuthenticationFilter;
import com.buli.blmall.admin.core.security.access.UserAccessPermission;
import com.buli.blmall.admin.core.security.provider.PasswordAuthProvider;
import com.buli.blmall.admin.core.security.provider.PhoneSmsAuthProvider;
import com.buli.blmall.admin.utils.ServletUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @author xiang.gao
 * @date 2025/2/28 11:09
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private AuthenticationFilter authenticationFilter;

    @Autowired
    private PhoneSmsAuthProvider phoneSmsAuthProvider;

    @Autowired
    private PasswordAuthProvider passwordAuthProvider;

    @Autowired
    private UserAccessPermission authAccessPermission;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        /**
         * 1. 用户登录并生成 JWT Token, PasswordAuthProvider(密码登录), PhoneSmsAuthProvider(短信验证码登录)
         * 2. 客户端RequestHeader携带 JWT Token 发起请求,
         * 3. AuthenticationFilter 过滤器处理请求 获取请求JWT token,并解析用户信息,设置认证信息
         * 4. UserAccessPermission 鉴权认证 判断用户是否有权限访问
         * 5. 请求完成处理 UnAuthenticationHandler(未登录处理), UserAccessDeniedHandler(权限不足处理)
         */
        http.authorizeHttpRequests((requests) -> requests
                        //不需要认证的路径
                        .requestMatchers(PathConstst.EXCLUDE_PATH).permitAll()
                        //允许所有OPTION请求
                        .requestMatchers(HttpMethod.OPTIONS).permitAll()
                        //其他路径都需要认证
                        .anyRequest()
                        //自定义权限控制
                        .access(authAccessPermission)
                )
                //取消默认登录页面
                .formLogin(AbstractHttpConfigurer::disable)
                //取消默认登出页面
                .logout(AbstractHttpConfigurer::disable)
                //禁用csrf保护，前后端分离不需要
                .csrf(AbstractHttpConfigurer::disable)
                //禁用session，因为我们已经使用了token
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                //认证失败处理 未登录、权限不足
                .exceptionHandling(c -> c.authenticationEntryPoint(authenticationEntryPoint()).accessDeniedHandler(accessDeniedHandler()))
                //禁用httpBasic，因为我们传输数据用的是post，而且请求体是JSON
                .httpBasic(AbstractHttpConfigurer::disable)
                //禁用匿名用户
                .securityContext(context -> context.requireExplicitSave(false)).anonymous(AbstractHttpConfigurer::disable)
                //将用户授权时用到的JWT校验过滤器添加进SecurityFilterChain中，并放在UsernamePasswordAuthenticationFilter的前面
                .addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class)
        ;
        return http.build();
    }

    /**
     * AuthenticationEntryPoint 由 ExceptionTranslationFilter 用来启动身份认证方案。
     * 它是一个入口点，用于检查用户是否已通过身份认证，如果用户已经认证，则登录该用户，否则抛出异常（unauthorized）
     */
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (request, response, authException) -> ServletUtil.write(response, Result.unauthorized().toString(), Constants.JSON_CONTENT_TYPE);
    }

    /**
     * 权限不足处理
     */
    @Bean
    public AccessDeniedHandler accessDeniedHandler() {
        return (request, response, accessDeniedException) -> ServletUtil.write(response, Result.denied().toString(), Constants.JSON_CONTENT_TYPE);
    }

    /**
     * 授权管理
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
        //账号密码登录
        authenticationManagerBuilder.authenticationProvider(passwordAuthProvider);
        //短信验证码登录
        authenticationManagerBuilder.authenticationProvider(phoneSmsAuthProvider);
        return authenticationManagerBuilder.build();
    }


}
